/**
 *
 */
package de.cbf.cam.dsa.services.impl.rcp_provider;

import java.util.List;
import java.util.UUID;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import de.cbf.cam.dsa.rcp.model.entities.CultureDSA;
import de.cbf.cam.dsa.rcp.model.entities.ProfessionDSA;
import de.cbf.cam.dsa.rcp.model.entities.RCPDefaultWeights;
import de.cbf.cam.dsa.rcp.model.entities.RaceDSA;
import de.cbf.cam.dsa.services.interfaces.rcp_provider.IProviderEntity;
import de.cbf.cam.dsa.services.interfaces.rcp_provider.IServiceDSAProviderCulture;
import de.cbf.cam.dsa.services.interfaces.rcp_provider.IServiceDSAProviderProfession;
import de.cbf.cam.dsa.services.interfaces.rcp_provider.IServiceDSAProviderRCPStructure;
import de.cbf.cam.dsa.services.interfaces.rcp_provider.IServiceDSAProviderRace;
import de.cbf.cam.dsa.services.interfaces.rcp_provider.IServiceDSARCPFinder;
import de.cbf.cam.dsa.services.interfaces.rcp_provider.exceptions.CultureNotFoundException;
import de.cbf.cam.dsa.services.interfaces.rcp_provider.exceptions.EntityNotFoundException;
import de.cbf.cam.dsa.services.interfaces.rcp_provider.exceptions.ProfessionNotFoundException;
import de.cbf.cam.dsa.services.interfaces.rcp_provider.exceptions.RaceNotFoundException;
import de.cbf.util.uuidheader.entities.EntityWeighted;
import de.cbf.util.uuidheader.entities.UUIDReference;
import de.cbf.util.uuidheader.entities.UUIDHeader;
import de.cbf.util.uuidheader.util.EntityWeightedList;

/**
 * @author salter
 * TODO implement real structure provider
 * this class will only do calculation and get the data to calculate from elsewhere
 */
public class RCPFinder implements IServiceDSARCPFinder {
	private static final String NAME = "RCP Finder";

	private static final Log logger = LogFactory.getLog(RCPFinder.class);

	protected IServiceDSAProviderRace			providerRaces 		;
	protected IServiceDSAProviderCulture 		providerCultures 	;
	protected IServiceDSAProviderProfession 	providerProfessions ;
	protected IServiceDSAProviderRCPStructure 	providerStructure ;

	protected RCPDefaultWeights defaultWeights;

	/**
	 *
	 */
	public RCPFinder() {

	}

	public UUIDHeader resolveEntity(UUIDReference nameUUID, String provider) throws EntityNotFoundException {
		UUIDHeader result = null;
		UUID uuid = nameUUID.getSavedUuid();
		if(PROVIDER_RACE.equals(provider)){
			return resolveEntity(uuid, providerRaces);
		}
		if(PROVIDER_CULTURE.equals(provider)){
			return resolveEntity(uuid, providerCultures);
		}
		if(PROVIDER_PROFESSION.equals(provider)){
			return resolveEntity(uuid, providerProfessions);
		}
		logger.error("specified provider not found: "+provider);
		return result;
	}

	protected UUIDHeader resolveEntity(UUID uuid,IProviderEntity providerEntities)throws EntityNotFoundException {
		if(uuid == null ||providerEntities == null){
			throw new EntityNotFoundException("Information missing:\n" +
					"\t UUID:" + uuid+"\n"+
					"\t Provider:" + providerEntities);
		}
		UUIDHeader entity = providerEntities.getEntityByItsID(uuid);
		return entity;
	}

	protected EntityWeightedList resolveEntitys(EntityWeightedList list, IProviderEntity providerEntities){
		EntityWeightedList result = new EntityWeightedList();
		for (EntityWeighted entityWeighted : list) {
			UUIDHeader entity = entityWeighted.getEntity();
			UUID uuid = null;
			//extract UUID
			if (entity instanceof UUIDReference) {
				UUIDReference nameUUID = (UUIDReference) entity;
				uuid = nameUUID.getSavedUuid();
			}
//			if (entity instanceof IUUIDHeader) {
//				IUUIDHeader header = (IUUIDHeader) entity;
//				uuid = header.getUuid();
//			}

			UUIDHeader fullEntity;
			if(uuid != null){
				try {
					fullEntity = resolveEntity(uuid, providerEntities);
					EntityWeighted newEntityWeighted = entityWeighted.clone();
					newEntityWeighted.setEntity(fullEntity);
					result.add(newEntityWeighted);
				} catch (EntityNotFoundException e) {
					logger.error(e.getMessage());
				} catch (CloneNotSupportedException e) {
					logger.error(e.getMessage());
				}
			}else{
				logger.error("Entity in list could not be resolved, because it does not carry UUID\n" +
						"Class is "+entity != null?entity.getClass().getSimpleName():"null");
			}
		}
		return result;
	}

	/* (non-Javadoc)
	 * @see de.cbf.cam.dsa.services.interfaces.rcp_provider.IServiceDSAProviderRCPStructure#findRacesBy(de.cbf.cam.dsa.rcp.model.entities.CultureDSA, de.cbf.cam.dsa.rcp.model.entities.ProfessionDSA)
	 */
	public EntityWeightedList findRacesBy(CultureDSA culture, ProfessionDSA profession) throws RaceNotFoundException {
		EntityWeightedList result = new EntityWeightedList();
		if(culture == null && profession == null){
			//unconstrained, returns all
			logger.debug("race:culture and profession not set, returning all races");
			//TESTWISE unconstrained returns all races
			//TODO races could be provided with weights according to continent/region etc, depends on this provider
			List<RaceDSA> allRaces = providerRaces.provideAllRaces();
			for (RaceDSA raceDSA : allRaces) {
				result.add(new EntityWeighted(raceDSA, 1D));
			}
			return result;
		}
		if(culture == null){
			String msg = "race:culture not set, computing by profession only";
			logger.debug(msg);
			result.addAll(providerStructure.provideRacesByProfession(profession));//profession cannot be <null>, would have triggered above
		}
		if(profession == null){
			String msg = "race:profession not set, computing by culture only";
			logger.debug(msg);
			result.addAll(providerStructure.provideRacesByCulture(culture));//culture cannot be <null>, would have triggered above
		}
		if(culture != null && profession != null ){
			//SECURITY just check if the profession is possible from the culture and return races of culture
			EntityWeightedList professionsInCulture = providerStructure.provideProfessionsByCulture(culture);
			UUIDReference professionUUID = UUIDReference.extractUUIDReference(profession);
			if( professionsInCulture.indexOfEntity(professionUUID) == -1){
				String errorMsg = "Profession is not possible from chosen culture, so no race can be provided";
				throw new RaceNotFoundException(errorMsg);
			}
			//can only take race data from culture, profession does not have :-)
			result.addAll(providerStructure.provideRacesByCulture(culture));
		}
		//throw exception if no race found
		if(result.isEmpty()){
			throw new RaceNotFoundException("No race found, that matches combination");
		}
		//TODO maybe adjust weights by defaultWeights

		//resolve nameUUID to full entities
		result = resolveEntitys(result, providerRaces);
		return result;
	}

	/* (non-Javadoc)
	 * @see de.cbf.cam.dsa.services.interfaces.rcp_provider.IServiceDSAProviderRCPStructure#findCulturesBy(de.cbf.cam.dsa.rcp.model.entities.RaceDSA, de.cbf.cam.dsa.rcp.model.entities.ProfessionDSA)
	 */
	public EntityWeightedList findCulturesBy(RaceDSA race, ProfessionDSA profession)throws CultureNotFoundException {
		EntityWeightedList result = new EntityWeightedList();
		if(race == null && profession == null){
			//unconstrained, returns all
			logger.debug("culture:race and profession not set, returning all cultures");
			//TESTWISE unconstrained returns all cultures
			//TODO cultures could be provided with weights according to continent/region etc, depends on this provider
			List<CultureDSA> allCultures = providerCultures.provideAllCultures();
			for (CultureDSA cultureDSA : allCultures) {
				result.add(new EntityWeighted(cultureDSA, 1D));
			}
			return result;
		}
		if(race == null){
			String msg = "culture:race not set, computing by profession only";
			logger.debug(msg);
			result.addAll(providerStructure.provideCulturesByProfession(profession));//profession cannot be <null>, would have triggered above

		}
		if(profession == null){
			String msg = "culture:profession not set, computing by race only";
			logger.debug(msg);
			result.addAll(providerStructure.provideCulturesByRace(race));//culture cannot be <null>, would have triggered above
		}
		if(race != null && profession != null){
			//if race is not set(null), empty list will be returned
			result.addAll(providerStructure.provideCulturesByRace(race));

			EntityWeightedList professionCultures = providerStructure.provideCulturesByProfession(profession);
			//if profession is null empty list will be returned
			result.mergeList(professionCultures, EntityWeightedList.BEHAVIOUR_LIST_RETAIN,EntityWeightedList.BEHAVIOR_WEIGHTS_MIDDLE);
		}

		if(result.isEmpty()){
			String errorMsg = "not a possible combination of race+profession, -> no culture can be resolved";
			throw new CultureNotFoundException(errorMsg);
		}
		//TODO maybe adjust weights by defaultWeights

		//resolve nameUUID to full entities
		result = resolveEntitys(result, providerCultures);

		return result;
	}



	/* (non-Javadoc)
	 * @see de.cbf.cam.dsa.services.interfaces.rcp_provider.IServiceDSAProviderRCPStructure#findProfessionsBy(de.cbf.cam.dsa.rcp.model.entities.RaceDSA, de.cbf.cam.dsa.rcp.model.entities.CultureDSA)
	 */
	public EntityWeightedList findProfessionsBy(RaceDSA race, CultureDSA culture) throws ProfessionNotFoundException{
		EntityWeightedList result = new EntityWeightedList();
		if(race == null && culture == null){
			//unconstrained, returns all
			logger.debug("profession:race and culture not set, returning all professions");
			//TESTWISE unconstrained returns all professions
			//TODO professions could be provided with weights according to continent/region etc, depends on this provider
			List<ProfessionDSA> allProfessions = providerProfessions.provideAllProfessions();
			for (ProfessionDSA professionDSA : allProfessions) {
				result.add(new EntityWeighted(professionDSA, 1D));
			}
			return result;
		}
		if(culture == null){
			String msg = "profession:culture is not set, so professions are compute from race only";
			logger.debug(msg);
			result.addAll(providerStructure.provideProfessionsByRace(race));
		}
		if(race == null){
			String msg = "profession:race is not set, so professions are compute from culture only";
			logger.debug(msg);
			result.addAll(providerStructure.provideProfessionsByCulture(culture));
		}
		if(race != null && culture != null){
			//SECURITY just check if culture possible from race
			EntityWeightedList culturesInRace = providerStructure.provideCulturesByRace(race);
			UUIDReference cultureUUID = UUIDReference.extractUUIDReference(culture);//needs wrapping here
			if( culturesInRace.indexOfEntity(cultureUUID) == -1){
				String errorMsg = "Culture is not possible from chosen race, so no profession can be provided";
				throw new ProfessionNotFoundException(errorMsg);
			}
			//just return professions of culture
			result.addAll(providerStructure.provideProfessionsByCulture(culture));
		}

		//throw exception if no race found
		if(result.isEmpty()){
			throw new ProfessionNotFoundException("No profession found, that matches combination");
		}
		//TODO maybe adjust weights by defaultWeights

		//resolve nameUUID to full entities
		result = resolveEntitys(result, providerProfessions);
		return result;
	}

	@Override
	public String getName() {
		return NAME;
	}

	public IServiceDSAProviderRace getProviderRaces() {
		return providerRaces;
	}

	public void setProviderRaces(IServiceDSAProviderRace providerRaces) {
		this.providerRaces = providerRaces;
	}

	public IServiceDSAProviderCulture getProviderCultures() {
		return providerCultures;
	}

	public void setProviderCultures(IServiceDSAProviderCulture providerCultures) {
		this.providerCultures = providerCultures;
	}

	public IServiceDSAProviderProfession getProviderProfessions() {
		return providerProfessions;
	}

	public void setProviderProfessions(
			IServiceDSAProviderProfession providerProfessions) {
		this.providerProfessions = providerProfessions;
	}

	public IServiceDSAProviderRCPStructure getProviderStructure() {
		return providerStructure;
	}

	public void setProviderStructure(
			IServiceDSAProviderRCPStructure providerStructure) {
		this.providerStructure = providerStructure;
	}

	public RCPDefaultWeights getDefaultWeights() {
		return defaultWeights;
	}

	public void setDefaultWeights(RCPDefaultWeights defaultWeights) {
		this.defaultWeights = defaultWeights;
	}

}
